﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace NRank.Common
{
	/// <summary>
	/// Class caching items from given collection when enumerated. Multiple enumeration of cached 
	/// collection performs only single enumeration of the inner collection, items are cached.
	/// </summary>
	/// <typeparam name="T">Type of items</typeparam>
	public class Cached<T> : IEnumerable<T>
	{
		/// <summary>
		/// Cached items
		/// </summary>
		private readonly List<T> cachedItems = new List<T>();

		/// <summary>
		/// Original source
		/// </summary>
		private readonly IEnumerable<T> originalItems;

		/// <summary>
		/// Action perform on eachrad
		/// </summary>
		private readonly Action<T> onReadItem;

		/// <summary>
		/// Creates instance of cached collection
		/// </summary>
		/// <param name="originalItems">Original collection to be cached</param>
		/// <param name="onReadItem">Action to be performed on each read</param>
		public Cached(IEnumerable<T> originalItems, Action<T> onReadItem = null)
		{
			this.originalItems = originalItems;
			this.onReadItem = onReadItem;
		}

		/// <summary>
		/// Determines if collection is already cached
		/// </summary>
		public bool IsCached { get; private set; }

		/// <summary>
		/// Decides wheather to use original items or cached items as source
		/// </summary>
		/// <returns>Original or cached items</returns>
		private IEnumerable<T> GetItems()
		{
			if (IsCached)
			{
				return cachedItems;
			}

			return OriginalItems();
		}

		/// <summary>
		/// Enumerates the original items and stores them into cache
		/// </summary>
		/// <returns>Original items</returns>
		private IEnumerable<T> OriginalItems()
		{
			foreach (var originalItem in originalItems)
			{
				cachedItems.Add(originalItem);
				if (onReadItem != null) onReadItem(originalItem);
				yield return originalItem;
			}

			IsCached = true;
		}

		/// <summary>
		/// Enumerator implementation
		/// </summary>
		/// <returns>Original or cached items</returns>
		public IEnumerator<T> GetEnumerator()
		{
			return GetItems().GetEnumerator();
		}

		/// <summary>
		/// Non-generic enumerator implementation
		/// </summary>
		/// <returns>Original or cached items</returns>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
	}

	/// <summary>
	/// Helper class for type inference./>
	/// </summary>
	public static class Cached
	{
		/// <summary>
		/// Converts collection to cached collection
		/// </summary>
		/// <typeparam name="T">Type of items</typeparam>
		/// <param name="items">Original items</param>
		/// <param name="onReadItem">Action to be performed when original item is read</param>
		/// <returns>Cached collection</returns>
		public static Cached<T> AsCached<T>(this IEnumerable<T> items, Action<T> onReadItem = null)
		{
			return new Cached<T>(items, onReadItem);
		}
	}
}